{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 1. Summary of Lab03" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you save your notebook as a program (menu -> File -> Download as -> Python (.py)) then you can edit that file, removing everything but the final functions.\n", "\n", "Then you can\n", "\n", "```python\n", "from lab04 import *\n", "```" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "format": "tab" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loaded lab04!\n" ] } ], "source": [ "import math\n", "import sys\n", "sys.path.append(\"/home/dblank\")\n", "from lab04 import *\n", "print(\"Loaded lab04!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Just a quick review of the state of Lab03:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1.1 Lab03, Exercise 5:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(- 5 3)\")" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2.0" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(/ 6 3)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1.2 Lab03, Exercise 6:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(quote 42)\")" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[1, [2, [3, '()']]]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(quote (1 2 3))\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1.3 Lab03, Exercise 7" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"#t\")" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(+ #t #f)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1.4 Lab03, Exercise 8" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"\"\"(if 1 1 2)\"\"\")" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"\"\"(if #f 1 2)\"\"\")" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"\"\"(if #t 1 (/ 2 0))\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 2. Sidebar: Readable Scheme Lists in Python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "sexp (structured expressions) composed of cons cells represented in Python are hard to read!" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[1, [2, [3, '()']]]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sexp([1, 2, 3])" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[1, [2, [3, '()']]]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cons(1, cons(2, cons(3, \"()\")))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[1, [[2, 4], [3, '()']]]" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cons(1, cons( cons(2, 4), cons(3, \"()\")))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One solution is to subclass list, and override the `__repr__` method:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class SchemeList(list):\n", " def __repr__(self):\n", " retval = \"\"\n", " current = self\n", " while isinstance(current, list):\n", " if retval:\n", " retval += \" \"\n", " retval += str(car(current))\n", " current = cdr(current)\n", " if current != \"()\": # proper list\n", " if retval:\n", " retval += \" \"\n", " retval += \". %s\" % current\n", " return \"(%s)\" % retval" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(1 2 3)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "SchemeList(sexp([1, 2, 3]))" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(1 (2 . 4) 3)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "SchemeList([1, [SchemeList([2, 4]), [3, \"()\"]]])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "then you only need use this method when you make a cons cell:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def cons(a, b):\n", " return SchemeList([a, b])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And voila!" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from lab05 import *" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(1 2 3)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(quote (1 2 3))\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compared to `[1, [2, [3, '()']]]` the Scheme-like repr of `(1 2 3)` looks quite nice! But remember that `(1 2 3)` really means `[1, [2, [3, '()']]]`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 3. Moving Function Definitions to an Environment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Currently, we have:\n", "\n", "```python\n", "def evaluator_apply(op, operands):\n", " if op == \"print\":\n", " Print(operands)\n", " elif op == \"+\":\n", " return car(operands) + cadr(operands)\n", " else:\n", " raise Exception(\"unknown apply operator: %s\" % op)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What we would like to do is add all of those definitions of functions to an **environment**. Then a language can add to them, remove them, and change them. Then op can just be an actual Python function:\n", "\n", "```python\n", "def evaluator_apply(op, operands):\n", " return op(operands)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To do this, we need to have an \"environment\" where the symbols (such as \"print\" and \"+\") can be associated with their actual functions. We will define an environment (or **env** for short) as a list of **frames** where a frame is a list of symbol-function pairs, such as:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": true }, "outputs": [], "source": [ "top_level_env = [\n", " # a frame:\n", " [\n", " [\"print\", Print],\n", " [\"+\", add_prim],\n", " ],\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A frame is a list of **bindings**. We say that \"+\" is bound the the add_prim function. add_prim is the value **bound** to the symbol \"+\"." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "def lookup(symbol, env):\n", " \"\"\"\n", " Lookup a symbol in an environment\n", " \"\"\"\n", " if ...:\n", " return ...\n", " else:\n", " raise Exception(\"no such variable: %s\" % symbol)\n", "```" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lookup(\"print\", top_level_env)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n" ] } ], "source": [ "temp_function = lookup(\"print\", top_level_env)\n", "temp_function(sexp([1, 2, 3]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "def evaluator(expr, env):\n", " if car(expr) == \"lit-exp\":\n", " return cadr(expr)\n", " elif car(expr) == \"var-exp\": ## NOW: look it up\n", " return lookup(cadr(expr), env)\n", " else:\n", " raise Exception(\"invalid ast: %s\" % expr)\n", "```" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "evaluator(scalc_parse(\"+\"), top_level_env)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "9" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(+ 5 4)\")" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [], "source": [ "top_level_env = [\n", " # a frame:\n", " [\n", " [\"print\", Print],\n", " [\"+\", add_prim],\n", " [\"pi\", math.pi],\n", " ],\n", "]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def scalc(string):\n", " return evaluator(scalc_parse(string), top_level_env)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "3.141592653589793" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"pi\")" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "6.283185307179586" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(+ pi pi)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 4. Defining your own Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Add lambda to parser\n", "2. Parse of a lambda is a procedure\n", "3. Evaluation of a procedure is a closure\n", "\n", "A procedure (proc-exp) is:\n", "\n", "* a list of symbols (the parameters)\n", "* the body of the lambda\n", "\n", "A closure is:\n", "\n", "* a list of symbols (the parameters)\n", "* the body of the lambda\n", "* the environment at the time that the procedure was evaluated" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(proc-exp (i) (var-exp i))" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc_parse(\"(lambda (i) i)\")" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(closure-exp (i) (var-exp i) [[['print', ], ['+', ], ['pi', 3.141592653589793]]])" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(lambda (i) i)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But now we have two types of functions to apply:\n", "\n", "* primitives (like add_prim)\n", "* closures\n", "\n", "We must change evaluator_apply such that an op (operator) can be a closure.\n", "\n", "When we get a closure, we must unpack the components:\n", "\n", "* symbols (the parameters)\n", "* body (code to evaluate)\n", "* the env in the closure\n", "\n", "Then to evaluate the body of the lambda, we extend the environment to include the symbols bound to the operands." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def evaluator_apply(op, operands, env):\n", " if isinstance(op, list) and car(op) == \"closure-exp\":\n", " symbols = cadr(op)\n", " body = caddr(op)\n", " closure_env = cadddr(op)\n", " return evaluator(body, \n", " extend_env(make_frame(symbols, operands), \n", " closure_env))\n", " else: # a Python function\n", " return op(operands)\n" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[['a', 1], ['b', 2], ['c', 3]]" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_frame(sexp([\"a\", \"b\", \"c\"]), sexp([1, 2, 3]))" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[[['a', 1], ['b', 2], ['c', 3]],\n", " [['print', ],\n", " ['+', ],\n", " ['pi', 3.141592653589793]]]" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "extend_env(make_frame(sexp([\"a\", \"b\", \"c\"]), sexp([1, 2, 3])), top_level_env)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"((lambda (i) i) 42)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the next section, I had to also add eq_prim and bind that to \"=\" in the environment." ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from lab05 import *" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"(= 42 43)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 5. Recursion in a language that doesn't support recursion" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "SCalc doesn't support a function calling itself. Why not?\n", "\n", "* we don't have define\n", "* we don't have assignment\n", "\n", "Try to write factorial:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "scalc(\"\"\" \n", "((lambda (n) \n", " (if (= n 1)\n", " 1\n", " (* n (??? (- n 1)))))\n", " 5)\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What should ??? actually be? It should be the function that we are defining. But we don't have a way of referring to it.\n", "\n", "But we could think of passing in something to act as ???:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "scalc(\"\"\" \n", "((lambda (n ???) \n", " (if (= n 1)\n", " 1\n", " (* n (??? (- n 1) ???))))\n", " 5\n", " ???\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hey, we could make a copy of the function and pass it in! We can change the ??? in the lambda to be `f` and we have a complete, real function:\n", "\n", "```python\n", "(lambda (n f) \n", " (if (= n 1)\n", " 1\n", " (* n (f (- n 1) f))))\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we make a copy of that, and pass it in as f:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "120" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"\"\" \n", "((lambda (n f) \n", " (if (= n 1)\n", " 1\n", " (* n (f (- n 1) f))))\n", " 5\n", " (lambda (n f) \n", " (if (= n 1)\n", " 1\n", " (* n (f (- n 1) f))))\n", " )\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whoa. We can do recursion in our language, even though it doesn't directly support recursion. This was a trick discovered in lambda calculus. In fact, you can \"factor out the recursion\" in the above to discover what is called the Y-combinator:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "120" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scalc(\"\"\"\n", "\n", "(((lambda (m)\n", " ((lambda (f) (m (lambda (a) ((f f) a))))\n", " (lambda (f) (m (lambda (a) ((f f) a))))))\n", "\n", "\n", " (lambda (factorial)\n", " (lambda (n)\n", " (if (= n 1)\n", " 1\n", " (* n (factorial (- n 1)))))))\n", " 5)\n", "\n", "\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Very cool!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 1 }